﻿using AiBLSmartEdu.Module.Auth.API.Configurations;
using AiBLSmartEdu.Module.Auth.API.DTOs;
using AiBLSmartEdu.Module.Auth.API.Interfaces;
using AutoMapper;
using Azure.Core;
using Domain.Entities;
using FrameworkCore.DI;
using FrameworkCore.Extensions;
using FrameworkCore.Helpers;
using FrameworkCore.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Claims;
using System.Text;

namespace AiBLSmartEdu.Module.Auth.API.Services;

#nullable disable
public class AuthServices : IScopedDependency
{
    private readonly IRepository<Customer> _customerRepository;
    private readonly ITokenService _tokenService;
    private readonly IOptions<JwtSettings> _optJwtSettings;
    private readonly IMapper _mapper;

    public AuthServices(
        IRepository<Customer> customerRepository,
        ITokenService tokenService,
        IOptions<JwtSettings> optJwtSettings,
        IMapper mapper)
    {
        _customerRepository = customerRepository;
        _tokenService = tokenService;
        _optJwtSettings = optJwtSettings;
        _mapper = mapper;
    }

    public async Task<LoginOutputModel> LoginAsync(LoginInputModel input) 
    {
        var customer =await _customerRepository.Query(q => q.Username == input.Username).FirstOrDefaultAsync();
        ExceptionHelper.ThrowIfNull(customer, "用户名或密码错误");
        ExceptionHelper.ThrowIfFalse(customer.VerifyPassword(input.Password), "用户名或密码错误");

        List<Claim> claims = new()
        {
            new Claim(ClaimTypes.NameIdentifier, customer.Id.ToString()),
            new Claim(ClaimTypes.Name, customer.Username)
        };
        var loginOutput = _mapper.Map<LoginOutputModel>(customer);
        loginOutput.AccessToken = await _tokenService.BuildAsync(claims, _optJwtSettings.Value);
        loginOutput.RefreshToken = await _tokenService.BuildAsync(claims, _optJwtSettings.Value, true);
        loginOutput.Expires = DateTime.Now.AddSeconds(_optJwtSettings.Value.ExpireSeconds).ToString("yyyy/MM/dd HH:mm:ss");

        return loginOutput;
    }

    public async Task<bool> RegisterAsync(RegisterInputModel input)
    {
        ExceptionHelper.ThrowIfFalse(input.Password == input.ConfirmPassword, "两次密码不一致");

        var userNameToExist = await _customerRepository.Query(x => x.Username == input.Username).AnyAsync();
        ExceptionHelper.ThrowIfTrue(userNameToExist, $"用户名：{input.Username}已存在");

        var emailToExist = await _customerRepository.Query(x => x.Email == input.Email).AnyAsync();
        ExceptionHelper.ThrowIfTrue(emailToExist, $"邮箱：{input.Email}已存在");

        var model = _mapper.Map<Customer>(input);
        model.HashPassword(input.Password);
        await _customerRepository.InsertAsync(model);
        return true;
    }

    public async Task<LoginOutputModel> RefreshTokenAsync(RefreshTokenInputModel input)
    {
        #region 验签
        var jwtSecurityToken = _tokenService.Decode(input.RefreshToken);
        var claims = jwtSecurityToken?.Claims?.ToArray();
        ExceptionHelper.ThrowIfFalse(claims.Any(), "登录过期，请重新登录！");
            

        var refreshExpires = claims!.FirstOrDefault(a => a.Type == "RefreshExpires")?.Value;
        ExceptionHelper.ThrowIfTrue(refreshExpires!.IsNullOrEmpty() || 
            refreshExpires!.ToInt64OrDefault() <= DateTime.Now.ToUnixTimestampSeconds(), "登录过期，请重新登录！");

        var securityKey = _optJwtSettings.Value.SecurityKey;
        var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256);
        var payload = jwtSecurityToken!.RawHeader + "." + jwtSecurityToken.RawPayload;
        ExceptionHelper.ThrowIfFalse(jwtSecurityToken.RawSignature == 
            JwtTokenUtilities.CreateEncodedSignature(payload, signingCredentials), "登录过期，请重新登录！");
        #endregion

        #region 刷新令牌
        var customerId = claims![0].Value.ToInt64OrDefault();
        var customer = await _customerRepository.Query(q => q.Id== customerId).FirstOrDefaultAsync();
        ExceptionHelper.ThrowIfNull(customer, "账号已被禁用");

        List<Claim> newClaims = new()
        {
            new Claim(ClaimTypes.NameIdentifier, customer.Id.ToString()),
            new Claim(ClaimTypes.Name, customer.Username)
        };
        var loginOutput = _mapper.Map<LoginOutputModel>(customer);
        loginOutput.AccessToken = await _tokenService.BuildAsync(newClaims, _optJwtSettings.Value);
        loginOutput.RefreshToken = await _tokenService.BuildAsync(newClaims, _optJwtSettings.Value, true);
        loginOutput.Expires = DateTime.Now.AddSeconds(_optJwtSettings.Value.ExpireSeconds).ToString("yyyy/MM/dd HH:mm:ss");
        #endregion

        return loginOutput;
    }
}
